class Vue {
  constructor(options) {
    const { beforeCreate, created, beforeMount, mounted } = options;
    if(beforeCreate && typeof(beforeCreate) == "function") beforeCreate.apply(this);
    this.$data = options.data;
    this.proxy();
    this.$watcher = {};
    this.observe();
    if(created && typeof(created) == "function") created.apply(this);
    if(beforeMount && typeof(beforeMount) == "function") beforeMount.apply(this);
    this.$el = document.querySelector(options.el);
    if(mounted && typeof(mounted) == "function") mounted.apply(this);
    this.$options = options;

    this.compile(this.$el);
  }
  proxy() {
    for(let key in this.$data) {
      Object.defineProperty(this, key, {
        get() {
          return this.$data[key];
        },
        set(val) {
          this.$data[key] = val;
        }
      })
    }
  }

  observe() {
    for(let key in this.$data) {
      let value = this.$data[key];
      let that = this;
      Object.defineProperty(this.$data, key, {
        get() {
          return value;
        },
        set(val) {
          value = val;
          if(that.$watcher[key]) {
            that.$watcher[key].forEach(watcher => {
              watcher.update();
            })
          }
        }
      })
    }
  }

  compile(dom) {
    dom.childNodes.forEach(child => {
      if(child.nodeType == 1) {
        if(child.getAttribute("@click")) {
          let btnKey = child.getAttribute("@click");
          const { methods } = this.$options;
          child.addEventListener("click", (e) => {
            methods[btnKey].apply(this, [e]);
          });
        }
        if(child.getAttribute("v-model")) {
          const key = child.getAttribute("v-model");
          child.value = this.$data[key];
          child.addEventListener("input", () => {
            this.$data[key] = child.value;
          });
        }
        if(child.childNodes && child.childNodes.length > 0) {
          this.compile(child);
        }
      }
      if(child.nodeType == 3) {
        const reg = /\{\{(.*?)\}\}/g;
        child.textContent = child.textContent.replace(reg, (...payload) => {
          let key = payload[1].trim();
          let watcher = new Watcher(this, key, child, "textContent");
          if(this.$watcher[key]) {
            this.$watcher[key].push(watcher);
          } else {
            this.$watcher[key] = [watcher];
          }
          return this.$data[key];
        });
      }
    });
  }
}

class Watcher {
  constructor(vNode, key, node, attr) {
    this.vNode = vNode;
    this.key = key;
    this.node = node;
    this.attr = attr;
  }
  update() {
    this.node[this.attr] = this.vNode[this.key];
  }
}
